#ifndef CELLS_HPP
#define CELLS_HPP

#include "meshTypes_fwd.hpp"
#include "cellsBase.hpp"
#include "edges.hpp"
#include "cell.hpp"
#include <experimental/memory>
#include <vector>

namespace hms {

template<>
class CellsTraits<Cells>{
public:
	using MeshType = Mesh;
	using AreasReturnType     = const ArrayXs&;
	using CircumfsReturnType  = const ArrayXs&;
	using CentroidsReturnType = const Matrix2Xs&;
};

class Cells : public CellsBase<Cells>
{
public:
	using Traits = CellsTraits<Cells>;
	using CentroidType = typename CellTraits<Cell>::CentroidType;
	using EdgeIndices  = typename CellTraits<Cell>::EdgeIndices;
	using AreasReturnType     = typename Traits::AreasReturnType;
	using CircumfsReturnType  = typename Traits::CircumfsReturnType;
	using CentroidsReturnType = typename Traits::CentroidsReturnType;
private:
	// std::experimental::observer_ptr<const Mesh> m_mesh {nullptr};
	
	/* each domain cell can have either 3 or 4 edge indices */
	ArrayX<char> m_nDomainCellEdges;
	Array4Xi      m_domainCellEdges;
	
	/* ghost cell edges are not stored. Their indices start at nDomainCells()
	 * and end at nDomainCells() + Mesh::nBoundaryEdges() - 1.
	 * Their constituting edges start at Mesh::nInnerEdges() and end at 
	 * Mesh::nEdges() - 1. */
	
	/* centroids and area are of the same type for domain and ghost cells */
	Matrix2Xs m_centroids;
	ArrayXs m_areas;
	ArrayXs m_circumfs;

public:
	Cells() = default;

	Cells(
		const Mesh&
	);
	
	/* minimal cost member functions */
	auto nDomainCellEdges() const -> const ArrayX<char>&;
	auto domainCellEdges () const -> const Array4Xi&;
	using CellsBase<Cells>::areas;
	using CellsBase<Cells>::centroids;
	
	
	auto nDomainCells() const -> Index;
	auto nGhostCells () const -> Index;
	auto nCells      () const -> Index;
	
	auto area      (Index) const -> scalar;
	auto circumf   (Index) const -> scalar;
	auto centroid  (Index) const -> CentroidType;
	auto nEdges    (Index) const -> char;
	auto edge      (Index) const -> Index;
	auto edges     (Index) const -> EdgeIndices;
	auto isGhost   (Index) const -> bool;
	auto domainCell(Index) const -> Cell;
	
	/* medium cost member functions */
	auto isBoundary(Index) const -> bool;
	
	/* high cost member functions */
	auto neighbours(Index) const -> ArrayXi;
	auto vertices  (Index, bool ordered = false) const -> ArrayXi;

private:
	auto centroidsImp() const -> CentroidsReturnType;
	auto areasImp    () const -> AreasReturnType;
	auto circumfsImp () const -> CircumfsReturnType;

	void nDomainCellEdges( ArrayX<char>&& );
	void domainCellEdges ( Array4Xi&& );
	void centroids       ( Matrix2Xs&& );
	void areas           ( ArrayXs&& );
	void storeData(
		ArrayX<char>&& nDomainCellEdges,
		Array4Xi&& domainCellEdges,
		const Ref<const Array4Xi>& domainCellVertices
	);
	void storeAreasAndCircumfs(
		const Ref<const Array4Xi>& domainCellVertices
	);
	
	/** @todo */
	void swap( Index, Index );
	
	friend class Mesh;
	friend CellsBase;
};

} // end namespace hms
#endif